using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using JWT;
using JWT.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;

namespace UploadServer.middlewares
{
    /// <summary>
    /// 一般上传
    /// </summary>
    public class UploadMiddleware : IMiddleware
    {
        private UploadServerConfig _config;

        public UploadMiddleware(IOptions<UploadServerConfig> config)
        {
            _config = config.Value;
        }

        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
            context.Response.Headers.Add("Access-Control-Allow-Headers", "content-type,jwt,origin");
            if (context.Request.Method.Equals(HttpMethods.Options, StringComparison.OrdinalIgnoreCase))
            {
                context.Response.StatusCode = (int) HttpStatusCode.OK;
            }
            else if (context.Request.Method.Equals(HttpMethods.Post, StringComparison.OrdinalIgnoreCase))
            {
                //验证jwt
                string token = null;
                if (context.Request.Headers.TryGetValue("jwt", out StringValues jwt))
                {
                    token = jwt.ToString();
                }
                else if (context.Request.Form.TryGetValue("jwt", out jwt))
                {
                    token = jwt.ToString();
                }
                else
                {
                    await context.Response.WriteAsync(new UploadResult()
                    {
                        msg = "No JWT in the header and form"
                    }.toJson());
                    return;
                }

                try
                {
                    var payload = new JwtBuilder().WithSecret(_config.JWTSecret).MustVerifySignature()
                        .Decode<JwtPayload>(token);
                    var msg = payload.validate();
                    if (msg != null)
                    {
                        await context.Response.WriteAsync(new UploadResult()
                        {
                            msg = msg
                        }.toJson());
                        return;
                    }

                    //特定的配置
                    var appConfig = _config.GetAppConfig(payload.app);

                    //跨域
                    context.Request.Headers.TryGetValue("Origin", out var origins);
                    var origin = origins.ToString();
                    if (!string.IsNullOrEmpty(origin) && appConfig.IsAllowOrigin(origin))
                    {
                        context.Response.Headers.Add("Access-Control-Allow-Origin", origin);
                    }

                    //获取上传的文件
                    var file = context.Request.Form.Files.FirstOrDefault();
                    if (file == null || file.Length == 0)
                    {
                        await context.Response.WriteAsync(new UploadResult()
                        {
                            msg = "There is no file data"
                        }.toJson());
                        return;
                    }

                    //大小验证
                    if (file.Length > (payload.GetByteSize() ?? _config.GetByteSize()))
                    {
                        await context.Response.WriteAsync(new UploadResult()
                        {
                            msg = "The file is too big"
                        }.toJson());
                        return;
                    }

                    //后缀验证
                    var ext = Path.GetExtension(file.FileName);
                    if (!(payload.exts + _config.AllowExts).Contains(ext, StringComparison.OrdinalIgnoreCase)
                        || appConfig.LimitExts.Contains(ext, StringComparison.OrdinalIgnoreCase))
                    {
                        await context.Response.WriteAsync(new UploadResult()
                        {
                            msg = "File extension is not allowed"
                        }.toJson());

                        return;
                    }

                    //上传逻辑
                    var now = DateTime.Now;
                    var yy = now.ToString("yyyy");
                    var mm = now.ToString("MM");
                    var dd = now.ToString("dd");

                    var fileName = Guid.NewGuid().ToString("n") + ext;

                    var folder = Path.Combine(_config.PhysicalPath, payload.app, yy, mm, dd);
                    if (!Directory.Exists(folder))
                    {
                        Directory.CreateDirectory(folder);
                    }

                    var filePath = Path.Combine(folder, fileName);

                    using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
                    {
                        file.CopyTo(fileStream);
                        fileStream.Flush(true);
                    }

                    var fileUrl = _config.RootUrl + "/" + payload.app + "/" + yy + "/" + mm +
                                  "/" +
                                  dd +
                                  "/" + fileName;

                    await context.Response.WriteAsync(new UploadResult()
                    {
                        ok = true,
                        url = fileUrl
                    }.toJson());
                }
                catch (TokenExpiredException)
                {
                    await context.Response.WriteAsync(new UploadResult()
                    {
                        msg = "Token has expired"
                    }.toJson());
                }
                catch (SignatureVerificationException)
                {
                    await context.Response.WriteAsync(new UploadResult()
                    {
                        msg = "Token has invalid signature"
                    }.toJson());
                }
            }
            else
            {
                await context.Response.WriteAsync(new UploadResult()
                {
                    msg = $"Request method '{context.Request.Method}' is not supported"
                }.toJson());
            }
        }
    }
}